package com.decent.manager.config.security;

import com.decent.common.util.BeanUtils;
import com.decent.manager.config.security.annotion.BasicPermission;
import com.decent.manager.config.security.annotion.NoAuthRequired;
import com.decent.manager.config.security.entrypoint.JwtAuthenticationEntryPoint;
import com.decent.manager.config.security.filter.JwtLoginFilter;
import com.decent.manager.config.security.filter.JwtVerifyFilter;
import com.decent.manager.system.auth.service.impl.AuthServiceImpl;
import com.decent.manager.system.service.SystemUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * SpringSecurity配置
 *
 * @author wangyan
 **/
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 放开权限校验的接口，运维心跳检测接口默认添加
     */
    public static final List<String> NONE_SECURITY_URL_PATTERNS = new ArrayList<String>(128) {
        private static final long serialVersionUID = 4991380371376950077L;

        {
            add("/systemMonitor/monitor");
        }
    };
    /**
     * 基础权限接口集合
     */
    public static final List<String> BASIC_PERMISSION_URL_PATTERNS = new ArrayList<>(128);

    @Resource
    private AuthServiceImpl authService;
    @Resource
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Resource
    private SystemUserService systemUserService;
    @SuppressWarnings({"unused", "下层获取applicationContext用，使用静态方法调用，但是需要注入"})
    @Resource
    private BeanUtils beanUtils;

    /**
     * 开启跨域访问拦截器
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        //开放哪些ip、端口、域名的访问权限，星号表示开放所有域
        List<String> origins = new ArrayList<>();
        origins.add("https://wechat.chengquan.cn");
        origins.add("http://wechat.chengquan.cn");
        origins.add("http://wx6.ejiaofei.com");
        origins.add("https://wx4.ejiaofei.com");
        origins.add("https://wx1.ejiaofei.com");
        origins.add("http://172.16.0.204:8000");
        origins.add("http://172.16.0.191:8000");
        config.setAllowedOrigins(origins);
        //允许跨域发送cookie
        config.setAllowCredentials(true);
        //允许HTTP请求中的携带哪些Header信息,*放行全部原始头信息
        config.addAllowedHeader("*");
        //开放哪些Http方法，允许跨域访问,*允许所有请求方法跨域调用
        config.addAllowedMethod("*");
        //暴露哪些头部信息（因为跨域访问默认不能获取全部头部信息）
        config.addExposedHeader(HttpHeaders.ACCEPT);
        //添加映射路径，“/**”表示对所有的路径实行全局跨域访问权限的设置
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);
        return new CorsFilter(configSource);
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtLoginFilter jwtLoginFilter() throws Exception {
        return new JwtLoginFilter(this.authenticationManager(), systemUserService, authService);
    }

    /**
     * 初始化时加载所有请求入口路由，标有“NoAuthRequired”无限制访问注解的加入静态list，鉴权时自动过滤list里的接口
     *
     * @return JwtVerifyFilter
     * @throws Exception 鉴权manager异常
     */
    @Bean
    public JwtVerifyFilter jwtVerifyFilter() throws Exception {
        Object bean = BeanUtils.getApplicationContext().getBean("requestMappingHandlerMapping");
        //noinspection unchecked
        AbstractHandlerMethodMapping<RequestMappingInfo> methodMapping =
                (AbstractHandlerMethodMapping<RequestMappingInfo>) bean;
        Map<RequestMappingInfo, HandlerMethod> requestMappings = methodMapping.getHandlerMethods();
        requestMappings.forEach((mappingInfo, method) -> {
            // 无需鉴权的接口集合
            if (Objects.nonNull(method.getMethodAnnotation(NoAuthRequired.class))) {
                assert mappingInfo.getPatternsCondition() != null;
                NONE_SECURITY_URL_PATTERNS.addAll(mappingInfo.getPatternsCondition().getPatterns());
                return;
            }
            // 需要鉴权但是不需要配置到资源表的接口集合
            if (Objects.nonNull(method.getMethodAnnotation(BasicPermission.class))) {
                assert mappingInfo.getPatternsCondition() != null;
                BASIC_PERMISSION_URL_PATTERNS.addAll(mappingInfo.getPatternsCondition().getPatterns());
            }
        });
        log.info("无需鉴权的路由集合[{}]", NONE_SECURITY_URL_PATTERNS);
        return new JwtVerifyFilter(this.authenticationManager(), authService);
    }

    /**
     * 认证用户的来源
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(authService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        String[] urls = new String[NONE_SECURITY_URL_PATTERNS.size()];
        httpSecurity.csrf().disable()
                //开启跨域访问
                .cors()
                .and()
                //不使用默认退出，自定义退出
                .logout().disable()
                // 未授权时访问须授权的资源端点
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                // 不需要授权的url
                .authorizeRequests().antMatchers(NONE_SECURITY_URL_PATTERNS.toArray(urls)).permitAll()
                // 其他都要授权
                .anyRequest().authenticated()
                .and()
                //登录和token校验过滤器
                .addFilterAt(jwtLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilter(jwtVerifyFilter())
                //全局不创建session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //禁用页面缓存
                .headers().frameOptions().disable().cacheControl();
    }
}
